"""
Redistribution and use in source and binary forms, with or
without modification, are permitted provided that the following conditions are met:

* Redistributions of source code must retain the above copyright notice, this
  list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
  notice, this list of conditions and the following disclaimer
  in the documentation and/or other materials provided with the distribution.
* Neither the names of the authers, nor the names of its contributors, may be used to endorse or 
  promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE REGENTS/COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND 
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
REGENTS/COPYRIGHT OWNERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

@Author Fredrique Samuels <fredriquesamuels@gmail.com>
@build.py  
"""

import os
import sys
from types import *
import time
import shutil

STATIC_LIB = 1
PROGRAM = 0


class MakeTarget:
    def __init__(self,name,SRC = None,TYPE=PROGRAM,OUTDIR = "",OUT = None):
        self.mSource = SRC
        self.mType = TYPE
        self.mName = name
        self.mOUTDIR = OUTDIR
        if OUT is None:
            self.mOUT = name
        else:
            self.mOUT = OUT
        self.mLists = {}
        self.mLists['INC'] = []
        self.mLists['FLAGS'] = ['O3','Wall','fmessage-length=0']
        self.mLists['MACRO'] = []
        self.mLists['LIB_DIR'] = []
        self.mLists['LIBS'] = []
        self.mLists['CPP'] = []
        
    def getList(self,prefix='',LIST=[],Quote = False):
        line = ""
        for index in range(len(LIST)):
            if index==0:
                if Quote:
                    line+=prefix+'\"'+LIST[index]+'\"'
                else:
                    line+=prefix+LIST[index]
            else:
                if Quote:
                    line+=' '+prefix+'\"'+LIST[index]+'\"'
                else:
                    line+=' '+prefix+LIST[index]
                
        return line
        
    def getFlags(self):
        return self.getList('-',self.mLists['FLAGS'])
        
    def getMacros(self):
        return self.getList('-D',self.mLists['MACRO'])
        
    def getLibDirs(self):
        return self.getList('-L',self.mLists['LIB_DIR'],Quote=True)
        
    def getSources(self):
        ret = self.getList('',self.mLists['CPP'])
        if not self.mSource is None:
            if len(self.mLists['CPP'])>0:
                ret+=' '
            ret+=self.mSource
        return ret
        
    def getTargetsToRebuild(self):
        SRCS = self.getSources().split(' ')
        ret = []
        for s in SRCS:
            obj = self.getObjName(s)
            if not os.path.isfile(obj):
                continue
            if os.path.getmtime(s) >= os.path.getmtime(obj):
                ret.append(obj)
                continue
        return ret
                
    def getIncludeDirs(self):
        return self.getList('-I',self.mLists['INC'],Quote=True)
        
    def getLibs(self):
        return self.getList('-l',self.mLists['LIBS'])
        
    def getObjects(self):
        LIST = []
        for cpp in self.getSources().split(' '):
            LIST.append(self.getObjName(cpp))
        ret = self.getList('',LIST)
        return ret
    
    def getObjName(self,src):
        return src.replace('.cpp','.o').replace('.c','.o').replace('.cc','.o')
        
    def getName(self):
        """Get the make file name"""
        return self.mName
        
    def add(self,kwargs):
        for key in kwargs.keys():
            if self.mLists.has_key(key):
                self.__append_to_list(self.mLists[key],kwargs[key])
            else:
                raise Exception("Invalid key :"+str(key)+"!!!!")

    def __append_to_list(self,l,files):
        if type(files) is ListType:
            for f in files:
                if not f in l:
                   l.append(f)
        elif type(files) is StringType:
            if not files in l:
                l.append(files)
        else:
            raise Exception ("files must be String or a list of Strings!!!!" )
        
    def getOutputFile(self):
        if self.mType == STATIC_LIB:
            return os.path.join(self.mOUTDIR,'lib'+self.mOUT).replace('\\','/')+".a"
        else:
            return os.path.join(self.mOUTDIR,self.mOUT).replace('\\','/')+".exe"
            
    def writeObjTarget(self,fh,src):
        line = ''
        obj = self.getObjName(src)
        line += '\n'+obj +' :'
        self.write(fh,line)
        line = '\t$(CC) -c'
        
        if len(self.getFlags()) > 0:
            line+=' '+self.getFlags()
            
        if len(self.getIncludeDirs()) > 0:
            line+=' '+self.getIncludeDirs()
            
        line+=' '+src+' -o'+obj
            
        self.write(fh,line)
        
    def writeProgramTarget(self,fh):
        line = ''
        line += '\n'+self.getName() +' : '+self.getObjects()
        self.write(fh,line)
        line = '\t$(CC) '
        key = self.getName()
        if len(self.getLibDirs()) > 0:
            line+=' '+self.getLibDirs()
            
        line+=' -o'+self.getOutputFile()
            
        if len(self.getObjects()) > 0:
            line+=' '+self.getObjects()
            
        if len(self.getFlags()) > 0:
            line+=' '+self.getFlags()
            
        if len(self.getMacros()) > 0:
            line+=' '+self.getMacros()
            
        if len(self.getLibs()) > 0:
            line+=' '+self.getLibs()
            
        self.write(fh,line)
        
    def writeStaticLibTarget(self,fh):
        line = ''
        line += '\n'+self.getName() +' : '+self.getObjects()
        self.write(fh,line)
        line = '\t$(LINKER) -r'
       
        line+=' '+self.getOutputFile()
            
        if len(self.getObjects()) > 0:
            line+=' '+self.getObjects()
            
        self.write(fh,line)
        
    def writeTarget(self,fh):
        if self.mType == STATIC_LIB:
            self.writeStaticLibTarget(fh)
        else:
            self.writeProgramTarget(fh)
        
    def __str__(self):
        ret = ""
        ret+="TARGET : "+self.getName()+'\n'
        ret+="FLAGS : "+self.getFlags()+'\n'
        ret+="OBJECTS : "+self.getObjects()+'\n'
        ret+="MACRO : "+self.getMacros()+'\n'
        ret+="INCLUDES_DIRS : "+self.getIncludeDirs()+'\n'
        ret+="LIB_DIRS : "+self.getLibDirs()+'\n'
        ret+="LIBS : "+self.getLibs()+'\n'
        
        return ret
        
    def write(self,fh,str):
        fh.write(str+'\n')
        
    def clean(self):
        OBJS = self.getObjects().split(" ")
        for obj in OBJS:
            if os.path.isfile(obj):
                try: 
                    os.remove(obj)
                except:
                    pass   
        out = self.getOutputFile()
        if os.path.isfile(out):
            try: 
                os.remove(out)
            except:
                pass

# Class for generating make files
class Make:
    def __init__(self,name="make"):
        self.mName = name
        self.mCC = "g++"
        self.mLINKER = 'ar'
        self.mTargets = {}
        self.msourceTargets = []

    def setCompiler(self,c):
        """Set the compiler to use
        
        @param c Compiler file
        """
        self.mCC = c
        
    def addTarget(self,trg,SRC=None,TYPE = PROGRAM,OUTDIR="",OUT = None):
        if '.' in trg:
            raise Exception("Target name cannot contain a \".\" character!!!")
        self.mTargets[trg] = MakeTarget(trg,SRC,TYPE,OUTDIR,OUT)
        
    def add(self,trg,**kwargs):
        self.mTargets[trg].add(kwargs)
        
    def saveToDisc(self):
        """Save the make file to disc"""
        self.msourceTargets = []
        fh = open(self.mName,'w')
        
        header = """###########################
# Autogenerated Makefile
# Do not modify !!!     
############################

"""
        
        fh.write(header)
        self.writeCompiler(fh)
        for key in self.mTargets.keys():
            target = self.mTargets[key]
            srcs = target.getSources().split(" ")
            for src in srcs:
                obj = self.getObjName(src)
                if not obj in self.msourceTargets:
                    self.msourceTargets.append(obj)
                    target.writeObjTarget(fh,src)
                    
        for key in self.mTargets.keys():
            self.mTargets[key].writeTarget(fh)
       
        fh.close()
    
    def getTarget(self,trg):
        return self.mTargets[trg]
       
        
    def write(self,fh,str):
        fh.write(str+'\n')
        
    def writeCompiler(self,fh):
        if self.mCC in ['g++']:
            self.write(fh,'CC='+self.mCC)
            self.write(fh,'LINKER='+self.mLINKER)
        else:
            raise Exception("Invalid Compiler : Try g++.")

  
        
    def getObjName(self,src):
        return src.replace('.cpp','.o').replace('.c','.o').replace('.cc','.o')
 
    def build(self,trg):
        print """\n*****Building Target : %s*****""" %(trg) 
        self.saveToDisc()
        make = 'mingw32-make'
        trgs = self.getTarget(trg).getTargetsToRebuild()
        for t in trgs:
            os.remove(t)
        out = self.getTarget(trg).getOutputFile()
        os.system(make+' -f '+self.mName+' '+trg)
        
    def clean(self,trg = None):
        if trg is None:
            for key in self.mTargets.keys():
                self.mTargets[key].clean()
        else:
            self.mTargets[trg].clean()

if __name__ == '__main__':
    make = Make('makeFile')
 
    make.addTarget('FuncVE',TYPE = STATIC_LIB,OUTDIR='lib')
    make.add('FuncVE',CPP='lib/func.cpp',MACRO=['FUNC2'])
  
    make.addTarget('app',SRC='main.cpp',OUT='demoApp')
    make.add('app',INC='lib',LIB_DIR=['lib'],LIBS=['FuncVE'],MACRO=['_FUNC2'])
    print make.getTarget('app')
   
    #make.saveToDisc()
    make.build('FuncVE')
    make.build('app')
    #print os.path.getmtime('main.cpp')
